package trash.Tmp;

import java.math.RoundingMode;

public class PrimitiveTypesTest
{
    public static void main(String[] args)
    {
        // В Java нормальный boolean, Нельзя писать так:
        // if (1) {}
        // Ну и вообще НЕТ преобразований boolean <-> int
        {
            String s = new Boolean(false).toString();
            Boolean b = Boolean.parseBoolean(s);
        }

        // Данное выражение всегда выполняется для любых a и b
        // (a ^ b) == (a != b)
        {
            boolean[] arrA = {false, true};
            boolean[] arrB = arrA.clone();
            for (boolean a : arrA)
            {
                for (boolean b : arrB)
                {
                    System.out.println((a ^ b) == (a != b));
                }
            }
        }

        // НЕТ const - только для ругательства, все константы через final
        //ERROR: const int x = 5;
        {
            final int X = 5;
            //ERROR: X = 10;  -- не получится

            final int Y;

            Y = 10;  // ВСЕ ОК, первая инициализация final-переменной

            //ERROR: Y = 20; // Нельзя повторно инициализировать final

        }

        // Character умеет работать не только с char 16 bit, но и с int 32 bit
        // Поддерживается UTF32 закодированное через суррогатную пару через (UTF16, UTF16)
        // Можно почитать стандарт Unicode для просветления
        {
            Character c1 = new Character('\uD800');
            Character c2 = Character.valueOf('\uDEB7');
            String s = "" + c1 + "" + c2;
        }

        // Сдвиги и знаковые типы
        // 1. При расширении знакового отрицательного числа влево новые биты заполняются 1 (кастование Byte -> Short)
        // 2. При знаковом (арифметическом) сдвиге вправо отрицательного числа биты заполняются 1
        // a === (a / b) * b + (a % b)
        {
            int x = 0xFFFFFFFF;
            System.out.println(Integer.toBinaryString(x >>> 24));  //0xFF
            System.out.println(Integer.toBinaryString(x >> 24)); //0xFFFFFFFF
            System.out.println(Integer.toBinaryString(x >>> 32));  //Не изменится, ибо X>>>Y происход как X>>>(Y%32)
        }

        // Вещешественные типы
        // Можно почитать стандарт IEEE754 для просветления
        //float     32бит   знак1бит   мантисса23бит   экспонента8бит
        //double    64бит   знак1бит   мантисса52бит   экспонента11бит
        //В бинарном виде: +/-M*2^E
        {
            float f1 = -123.45f;
            double d1 = -123.45e-2;
            // Можно в HEX-формате писать
            float fx = 0x10p12f; //(2^4) * (2^10)
            System.out.println(fx);
            // Инкремент тоже есть
            System.out.println(++fx);
            // Даже остаток от деления есть
            System.out.println(fx % 2);

            // Есть специальные значения: +INF, -INF, NAN
            // При переполнении экспоненты получим +/-INF
            // При переполнении мантиссы получим просто потерю точности
            float minf = -1.0f;
            for (int i = 0; i < 1024; ++i)
                minf *= 2;
            System.out.println(minf);  //-INF

            float inf = Float.MAX_VALUE;
            inf += 100000;
            System.out.println(inf == Float.MAX_VALUE); //true

            // Деление на 0 дает +/-INF
            float pinf = 1245.56f;
            pinf /= 0;
            System.out.println(pinf);  //+INF

            // Деление 0 на 0 дает NaN
            float nan1 = 0;
            float zero = 0;
            nan1 /= zero;
            System.out.println(nan1);  //NAN

            // Что интересно NAN != NAN
            float nan2 = 0f/0f;
            System.out.println(nan2);

            System.out.println(nan1 == nan2);  // всегда false

            // Однако можно проверить что это NaN методом
            System.out.println(Float.isNaN(nan1));  //true

            // 0 * INF тоже NAN
            System.out.println(Float.POSITIVE_INFINITY * 0f);  //NAN

            // +INF + -INF = NAN
            System.out.println(Float.POSITIVE_INFINITY + Float.NEGATIVE_INFINITY);  //NAN

            // Java использует регистры FPU для операций с Float/Double
            // Размеры регистров могут быть не 32/64 бита, а 80бит как на x86.
            // То есть получится результат точнее, чем ожидалось
            // Если нужно поведение Float32/Double64, то используется модификатор strictfp к классу или методу
        }

        {
            // Кастование типов
            // При расширении положительного целого без знака старшие биты становятся нулями
            byte b0 = 1;
            short sb0 = b0;
            System.out.println(Short.valueOf(sb0).toString());
            // При расширении отрицательного целого со знаком знак копируется во все сташие биты
            byte b1 = -1;
            short sb1 = b1;
            System.out.println(Short.valueOf(sb1).toString());

            // Сужении что положительного что отрицательного числа просто отбрасываются старшие биты
            System.out.println((short)-1);  //-1
            System.out.println((byte)256); //0

            // При приведении int -> float может потеряться точность последних знаков
            System.out.println("MAX_INT " + Integer.MAX_VALUE + " Cast MAX_INT to Float: " + (float)Integer.MAX_VALUE);

            // При приведении float -> int максимум что можно получить это MAX_INT, заворачивания не будет
            System.out.println((int)10_000_000_000f == Integer.MAX_VALUE);  //true

            // При приведении double -> float можно получить +/- INF если экпонента не влезает
            double d = -Float.MAX_VALUE * 2;
            System.out.println((float)d);  //-INF

            // !!!!!!! КАЖДОЕ ИЗ ЧИСЕЛ КАСТУЕТСЯ
            // При вычислении A op B
            // Аргументы A и B преобразуются к типам:
            // 1. Если одно из чисел double, то double
            // 2. Иначе если одно из чисел float, то float
            // 3. Иначе если одно из чисел long, то long
            // 4. Иначе оба числа кастуются в int

            // Следствие:
            // Арифметическое выражение над byte, short, char имеет тип int. Нужно обратное явное кастование

            byte b = 10;
            byte c = (byte)(b + 1);

            // (float или double) -> (byte, short, int) это сначала кастуется в int
            // В JVM нет типов byte, short, они эмулируются через int
            short s = (short)4e9f;
            System.out.println(s); //-1
            System.out.println((byte)4e9d); //-1

            // А здесь не будет ругаться компилятор насчет типа
            int x = 0;
            x += 0x1_00_00_00_00L;  //x = 0, потому что x = (int)(x + 0x1_00_00_00_00L)
            System.out.println(x);

            // в Integer, Long, Short, Byte есть много полезные методов, javadoc JDK 8 в помощь
            System.out.println(Integer.rotateLeft(0x80000000, 1));  //1, циклический сдвиг влево

            // Когда нужна длинная целая арифметика
            java.math.BigInteger bi = new java.math.BigInteger("1");
            for (int i = 2; i < 100; ++i)
                bi = bi.multiply(java.math.BigInteger.valueOf(i));

            System.out.println("100! = " + bi);

            // Когда нужна длинная вещественная арифметика
            java.math.BigDecimal bd = new java.math.BigDecimal("1.0");
            bd = bd.divide(new java.math.BigDecimal("3.0"), 100, RoundingMode.FLOOR);
            System.out.println("1/3 = " + bd);

        }
    }
}
